﻿using System;
using System.Collections.Generic;

namespace DeepEarth.Core.Data
{
    public static class GeoItemViewModelHelper
    {
        public static void FindSeachableItemViewModel(IEnumerable<GeoItemViewModel> rootViewModels, List<IComparable> totalHierarchy, Action<GeoItemViewModel> callback)
        {
            // Trigger layer load
            LoadViewModelHierarchy(rootViewModels, totalHierarchy, 0, callback);
        }

        private static void LoadViewModelHierarchy(IEnumerable<GeoItemViewModel> viewModels, List<IComparable> hierarchy, int index, Action<GeoItemViewModel> callback)
        {
            GeoItemViewModel nextViewModel = FindLoadedViewModelWithId(hierarchy[index], viewModels);

            if (nextViewModel != null)
            {
                // The viewmodel we're looking for
                if (index == hierarchy.Count - 1)
                {
                    callback(nextViewModel);
                }                
                else if (nextViewModel.Data is ILazyLoad && (!nextViewModel.IsExpanded || !nextViewModel.IsActive))
                {
                    // Lazy Load and not loaded. Trigger a load.
                    System.Collections.Specialized.NotifyCollectionChangedEventHandler handler = null;
                    int nextIndex = index + 1;
                    handler = (o, e) => LoadViewModelHierarchy(nextViewModel.Children, hierarchy, nextIndex, callback);

                    nextViewModel.Children.CollectionChanged += handler;

                    nextViewModel.IsExpanded = true;
                    nextViewModel.IsActive = true;

                    // try to search immediately in case it has already been loaded
                    LoadViewModelHierarchy(nextViewModel.Children, hierarchy, nextIndex, callback);
                }
                else
                {
                    // not a lazy load. Children should be loaded, keep traverse down the hierarchy
                    nextViewModel.IsExpanded = true;
                    nextViewModel.IsActive = true;
                    LoadViewModelHierarchy(nextViewModel.Children, hierarchy, ++index, callback);
                }
            }
            else
            {
                // if null, do nothing. The collection is currently being loaded and the collection changed handler
                // should execute in future.
            }
        }

        private static GeoItemViewModel FindLoadedViewModelWithId(IComparable id, IEnumerable<GeoItemViewModel> viewModels)
        {
            foreach (GeoItemViewModel viewModel in viewModels)
            {
                var searchableItem = viewModel.Data as ISearchable;

                if (searchableItem != null && searchableItem.Id != null && searchableItem.Id.Equals(id))
                {
                    return viewModel;
                }
            }

            return null;
        }

        private static void FindNestedViewModelWithId(IComparable Id, IEnumerable<GeoItemViewModel> viewModels, Action<GeoItemViewModel> callback)
        {
            // There is an assumption that the first list of viewmodels has been fully loaded. i.e. the top level list is complete.
            GeoItemViewModel target = FindLoadedViewModelWithId(Id, viewModels);

            if (target != null)
            {
                callback(target);
                return;
            }
            else
            {
                foreach (GeoItemViewModel parentViewModel in viewModels)
                {
                    // See if the item is already loaded
                    target = FindLoadedViewModelWithId(Id, parentViewModel.Children);

                    if (target != null)
                    {
                        callback(target);
                        return;
                    }
                }

                foreach (GeoItemViewModel parentViewModel in viewModels)
                {
                    // None of the (loaded) child collections contained the viewmodel that we're looking for
                    // Attempt to load children if not already loaded.
                    ILazyLoad lazyLoad = parentViewModel.Data as ILazyLoad;

                    if (lazyLoad != null && lazyLoad.LoadState != LazyLoadState.Loaded)
                    {
                        parentViewModel.Children.CollectionChanged += (o, e) =>
                            {
                                FindNestedViewModelWithId(Id, parentViewModel.Children, callback);
                            };

                        parentViewModel.IsExpanded = true;
                    }

                }
            }
        }

        public static void FindSearchableItemViewModel(IEnumerable<GeoItemViewModel> rootViewModels, IComparable parentId, IComparable itemId, Action<GeoItemViewModel> callback)
        {
            // This is the callback that is called when the parent view model is found
            Action<GeoItemViewModel> internalCallback = (parentViewModel) =>
                {
                    FindNestedViewModelWithId(itemId, new List<GeoItemViewModel> { parentViewModel }, callback);
                };

            // Find the parent view model. This can appear anywhere in the hierarchy. Once this is found, find the item's view model.
            FindNestedViewModelWithId(parentId, rootViewModels, internalCallback);
        }

        /// <summary>
        /// Asynchronously search for the Loaded ViewModel that encapsulates the given model. This method will not expand ILazyLoads that have not been loaded
        /// </summary>
        public static void FindLoadedItemViewModel(IEnumerable<GeoItemViewModel> viewModels, object item, Action<GeoItemViewModel> callback)
        {
            System.Threading.ThreadPool.QueueUserWorkItem((state) =>
                {
                    GeoItemViewModel found = FindLoadedItemViewModel(viewModels, item);

                    if (found != null)
                    {                        
                        callback(found);
                    }
                });
        }

        private static GeoItemViewModel FindLoadedItemViewModel(IEnumerable<GeoItemViewModel> viewModels, object item)
        {
            if (item == null)
            {
                return null;
            }

            foreach (GeoItemViewModel viewModel in viewModels)
            {
                if (viewModel.Data == item)
                {
                    return viewModel;
                }

                if (viewModel.Children != null)
                {
                    GeoItemViewModel found = FindLoadedItemViewModel(viewModel.Children, item);
                    
                    if (found != null)
                    {
                        return found;
                    }                    
                }
            }

            return null;
        }

        /// <summary>
        /// If the data associated with a view model is part of a composite element (A composite element is the element hierarchy created when a hierarchical object is decorated
        /// with the AtomicEntity Attribute) then find the view model associated with that top level object. This method helps with deleting from the map. If any element in a composite
        /// element is deleted, then the entire composite element should be deleted. To do that, we need to find the top level model and its corresponding view model.
        /// </summary>
        /// <param name="viewModel"></param>
        /// <returns></returns>
        public static GeoItemViewModel FindAtomicFeatureRoot(GeoItemViewModel viewModel)
        {
            GeoItemViewModel compositeViewModel = null;
            
            // check self
            if (viewModel.Data != null && AtomicFeatureAttribute.IsAtomic(viewModel.Data))
            {
                compositeViewModel = viewModel;
            }

            // check Ancestors
            if (viewModel.Parent != null)
            {
                GeoItemViewModel ancestorComposite = FindAtomicFeatureRoot(viewModel.Parent);

                if (ancestorComposite != null)
                {
                    compositeViewModel = ancestorComposite;
                }
            }

            return compositeViewModel;
        }

        public static GeoItemViewModel FindAtomicEntityRoot(GeoItemViewModel viewModel)
        {
            GeoItemViewModel compositeViewModel = null;

            // check self
            if (viewModel.Data != null && AtomicFeatureAttribute.IsAtomic(viewModel.Data))
            {
                compositeViewModel = viewModel;
            }

            // check Ancestors
            if (viewModel.Parent != null)
            {
                GeoItemViewModel ancestorComposite = FindAtomicFeatureRoot(viewModel.Parent);

                if (ancestorComposite != null)
                {
                    compositeViewModel = ancestorComposite;
                }
            }

            return compositeViewModel;
        }

        public static bool IsEntityAtomic(object o)
        {
            IItemsContainer container = o as IItemsContainer;

            return (container != null && container.IsAtomic) || AtomicEntityAttribute.IsAtomic(o);
        }

        public static bool HasAtomicAttribute(object o)
        {
            return AtomicEntityAttribute.IsAtomic(o);
        }

        /// <summary>
        /// Used exclusively by map. All other controls such as the layer panel should used IsEntityAtomic
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public static bool IsFeatureAtomic(object o)
        {
            return AtomicFeatureAttribute.IsAtomic(o);
        }
    }
}
